
#include <BFont>
#include <BPainter>
#include <BMouseEvent>

#include <BMenu>
#include "member_BMenu.h"
#include "member_BAction.h"
#include "member_BLayout.h"

using namespace BWE;

#define member					(*(member_BMenu*)_ptr)
#define member_allocate()		_ptr = new member_BMenu(this)
#define member_release()		delete (member_BMenu*)_ptr

BMenu::BMenu(BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->hide();
}
BMenu::BMenu(const BString& title, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->hide();
	this->setTitle(title);
}
BMenu::BMenu(BIcon* icon, const BString& title, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	this->hide();
	this->setTitle(title);
	this->setTitleIcon(icon);
}
BMenu::~BMenu()
{
	member_release();
}

void BMenu::setAlign(Align align)
{
	if (member.align != align)
	{
		member.align = align;
		member.dirty = true;
		this->fresh();
	}
}
Align BMenu::align() const
{
	return member.align;
}

void BMenu::setForm(Form form)
{
	if (member.form != form)
	{
		member.form = form;
		member.dirty = true;
		this->fresh();
	}
}
Form BMenu::form() const
{
	return member.form;
}

void BMenu::setSpacing(int spacing)
{
	if (member.spacing != spacing)
	{
		member.spacing = spacing;
		member.dirty = true;
		this->fresh();
	}
}
int BMenu::spacing() const
{
	return member.spacing;
}

void BMenu::setIconSize(const BSize& iconSize)
{
	if (member.iconSize != iconSize)
	{
		member.iconSize = iconSize;
		member.dirty = true;
		this->fresh();
	}
}
const BSize& BMenu::iconSize() const
{
	return member.iconSize;
}

bool BMenu::addAction(BAction* action)
{
	if (!action)
		return false;
	if (member.items.contain(action))
		return true;
	if (member.items.append(action))
	{
		member.connect(action, Signal_Renamed, &member_BMenu::slotActionRenamed);
		member.connect(action, Signal_TextChanged, &member_BMenu::slotActionTextChanged);
		member.connect(action, Signal_IconChanged, &member_BMenu::slotActionIconChanged);
		member.connect(action, Signal_Triggered, &member_BMenu::slotActionTriggered);
		member.connect(action, Signal_Checked, &member_BMenu::slotActionChecked);
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
BAction* BMenu::addAction(const BString& text)
{
	BAction* action = new BAction(text);
	addAction(action);
	return action;
}
BAction* BMenu::addAction(BIcon* icon, const BString& text)
{
	BAction* action = new BAction(icon, text);
	addAction(action);
	return action;
}

bool BMenu::insertAction(int pos, BAction* action)
{
	if (!action)
		return false;
	if (member.items.contain(action))
	{
		int index = member.items.find(action);
		member.items.move(index, pos);
		return true;
	}
	if (member.items.insert(pos, action))
	{
		member.connect(action, Signal_Renamed, &member_BMenu::slotActionRenamed);
		member.connect(action, Signal_TextChanged, &member_BMenu::slotActionTextChanged);
		member.connect(action, Signal_IconChanged, &member_BMenu::slotActionIconChanged);
		member.connect(action, Signal_Triggered, &member_BMenu::slotActionTriggered);
		member.connect(action, Signal_Checked, &member_BMenu::slotActionChecked);
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
BAction* BMenu::insertAction(int pos, const BString& text)
{
	BAction* action = new BAction(text);
	insertAction(pos, action);
	return action;
}
BAction* BMenu::insertAction(int pos, BIcon* icon, const BString& text)
{
	BAction* action = new BAction(icon, text);
	insertAction(pos, action);
	return action;
}

bool BMenu::removeAction(BAction* action)
{
	if (!action)
		return false;
	if (member.items.contain(action))
	{
		member.disconnect(action);
		member.items.remove(action);
		member.dirty = true;
		this->adjust();
		this->fresh();
		return true;
	}
	return false;
}
bool BMenu::removeAction(const BString& actionText)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BAction* action = member.items[i].action;
		if (action == 0)
			continue;
		if (action->text() == actionText)
		{
			member.disconnect(action);
			member.items.remove(i);
			member.dirty = true;
			this->fresh();
			return true;
		}
	}
	return false;
}

bool BMenu::addMenu(BMenu* menu)
{
	if (!menu)
		return false;
	if (menu == this)
		return false;
	if (member.items.contain(menu))
		return true;
	if (member.items.append(menu))
	{
		member.connect(menu, Signal_Renamed, &member_BMenu::slotMenuRenamed);
		member.connect(menu, Signal_TextChanged, &member_BMenu::slotActionTextChanged);
		member.connect(menu, Signal_IconChanged, &member_BMenu::slotActionIconChanged);
		member.connect(menu, Signal_TitleChanged, &member_BMenu::slotMenuTitleChanged);
		member.connect(menu, Signal_TitleIconChanged, &member_BMenu::slotMenuTitleChanged);
		member.connect(menu, Signal_Style, &member_BMenu::slotMenuStyleChanged);
		this->addChild(menu);
		menu->flush();
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
BMenu* BMenu::addMenu(const BString& title)
{
	BMenu* menu = new BMenu(title);
	addMenu(menu);
	return menu;
}
BMenu* BMenu::addMenu(BIcon* icon, const BString& title)
{
	BMenu* menu = new BMenu(icon, title);
	addMenu(menu);
	return menu;
}

bool BMenu::insertMenu(int pos, BMenu* menu)
{
	if (!menu)
		return false;
	if (member.items.contain(menu))
	{
		int index = member.items.find(menu);
		member.items.move(index, pos);
		return true;
	}
	if (member.items.insert(pos, menu))
	{
		member.connect(menu, Signal_Renamed, &member_BMenu::slotMenuRenamed);
		member.connect(menu, Signal_TextChanged, &member_BMenu::slotActionTextChanged);
		member.connect(menu, Signal_IconChanged, &member_BMenu::slotActionIconChanged);
		member.connect(menu, Signal_TitleChanged, &member_BMenu::slotMenuTitleChanged);
		member.connect(menu, Signal_TitleIconChanged, &member_BMenu::slotMenuTitleChanged);
		member.connect(menu, Signal_Style, &member_BMenu::slotMenuStyleChanged);
		this->addChild(menu);
		menu->flush();
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
BMenu* BMenu::insertMenu(int pos, const BString& title)
{
	BMenu* menu = new BMenu(title);
	insertMenu(pos, menu);
	return menu;
}
BMenu* BMenu::insertMenu(int pos, BIcon* icon, const BString& title)
{
	BMenu* menu = new BMenu(icon, title);
	insertMenu(pos, menu);
	return menu;
}

bool BMenu::removeMenu(BMenu* menu)
{
	if (!menu)
		return false;
	if (member.items.contain(menu))
	{
		this->removeChild(menu);
		member.disconnect(menu);
		member.items.remove(menu);
		member.dirty = true;
		this->adjust();
		this->fresh();
		return true;
	}
	return false;
}
bool BMenu::removeMenu(const BString& title)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BMenu* menu = member.items[i].menu;
		if (menu && menu->title() != title)
		{
			return this->removeMenu(menu);
		}
	}
	return false;
}

BAction* BMenu::action(int index)
{
	return member.items[index].action;
}
const BAction* BMenu::action(int index) const
{
	return member.items[index].action;
}

BAction* BMenu::action(const BString& text)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BAction* action = member.items[i].action;
		if (action && action->text() == text)
			return action;
	}
	return 0;
}
const BAction* BMenu::action(const BString& text) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BAction* action = member.items[i].action;
		if (action && action->text() == text)
			return action;
	}
	return 0;
}

BAction* BMenu::actionOfAsset(const BValue& value)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BAction* action = member.items[i].action;
		if (action && action->asset() == value)
			return action;
	}
	return 0;
}
const BAction* BMenu::actionOfAsset(const BValue& value) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BAction* action = member.items[i].action;
		if (action && action->asset() == value)
			return action;
	}
	return 0;
}

BAction* BMenu::findAction(const BString& text)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		MenuItem& item = member.items[i];
		if (item.action && item.action->text() == text)
			return item.action;
		if (!item.menu)
			continue;
		BAction* action = item.menu->findAction(text);
		if (action)
			return action;
	}
	return 0;
}
const BAction* BMenu::findAction(const BString& text) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		MenuItem& item = member.items[i];
		if (item.action && item.action->text() == text)
			return item.action;
		if (!item.menu)
			continue;
		BAction* action = item.menu->findAction(text);
		if (action)
			return action;
	}
	return 0;
}

BAction* BMenu::locateAction(const BString& path)
{
	if (path.empty())
		return 0;
	if(path.contain('/'))
	{
		BString title;
		BMenu* menu = member.locateMenu(path, title);
		return menu->action(title);
	}
	return findAction(path);
}
const BAction* BMenu::locateAction(const BString& path) const
{
	if (path.empty())
		return 0;
	if (path.contain('/'))
	{
		BString title;
		const BMenu* menu = member.locateMenu(path, title);
		return menu->action(title);
	}
	return findAction(path);
}

BMenu* BMenu::menu(int index)
{
	return member.items(index).menu;
}
const BMenu* BMenu::menu(int index) const
{
	return member.items(index).menu;
}

BMenu* BMenu::menu(const BString& title)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BMenu* menu = member.items[i].menu;
		if (menu && menu->title() == title)
			return menu;
	}
	return 0;
}
const BMenu* BMenu::menu(const BString& title) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BMenu* menu = member.items[i].menu;
		if (menu && menu->title() == title)
			return menu;
	}
	return 0;
}

BMenu* BMenu::menuOfAsset(const BValue& value)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		BMenu* menu = member.items[i].menu;
		if (menu && menu->asset() == value)
			return menu;
	}
	return 0;
}
const BMenu* BMenu::menuOfAsset(const BValue& value) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		const BMenu* menu = member.items[i].menu;
		if (menu && menu->asset() == value)
			return menu;
	}
	return 0;
}

BMenu* BMenu::findMenu(const BString& title)
{
	for (int i = 0; i < member.items.size(); i++)
	{
		MenuItem& item = member.items[i];
		if (item.menu && item.menu->title() == title)
			return item.menu;
		if (!item.menu)
			continue;
		BMenu* menu = item.menu->findMenu(title);
		if (menu)
			return menu;
	}
	return 0;
}
const BMenu* BMenu::findMenu(const BString& title) const
{
	for (int i = 0; i < member.items.size(); i++)
	{
		MenuItem& item = member.items[i];
		if (item.menu && item.menu->title() == title)
			return item.menu;
		if (!item.menu)
			continue;
		BMenu* menu = item.menu->findMenu(title);
		if (menu)
			return menu;
	}
	return 0;
}

BMenu* BMenu::locateMenu(const BString& path)
{
	if (path.empty())
		return 0;
	if (path.contain('/'))
	{
		BString title;
		BMenu* menu = member.locateMenu(path, title);
		return menu->menu(title);
	}
	return findMenu(path);
}
const BMenu* BMenu::locateMenu(const BString& path) const
{
	if (path.empty())
		return 0;
	if (path.contain('/'))
	{
		BString title;
		const BMenu* menu = member.locateMenu(path, title);
		return menu->menu(title);
	}
	return findMenu(path);
}

bool BMenu::addSeparator()
{
	if (member.items.append(MenuItem()))
	{
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}

void BMenu::clear()
{
	if (member.items.size())
	{
		while (member.items.size())
		{
			MenuItem& item = member.items.last();
			if (item.action)
				member.disconnect(item.action);
			if (item.menu)
				member.disconnect(item.menu);
			member.items.remove(member.items.size() - 1);
		}
		member.dirty = true;
		this->adjust();
		this->fresh();
	}
}
int BMenu::count() const
{
	return member.items.size();
}

BSize BMenu::sizeHint() const
{
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	BSize hsize = this->preferSize(member.conentSize);
	return hsize;
}

void BMenu::showEvent(const BEvent& showEvent)
{
	BSize hsize = this->sizeHint();
	this->setSize(hsize);
	BWidget* root = this->root();
	if (root != this)
	{
		BRect rootRect = root->size();
		BRect abRect = rootRect.wrap(this->absoluteRect());
		this->setAbsoluteRect(abRect);
	}
	this->setFocused(true);
}
void BMenu::activeEvent(const BEvent& event)
{
	if (this->actived() == false)
	{
		this->hide();
	}
}

void BMenu::resizeEvent(const BEvent& event)
{
	member.dirty = true;
	this->fresh();
}

void BMenu::styleEvent(const BEvent& event)
{
	if (const BStyle* style = event.value())
	{
		member.separatorImage = style->image(Image_Separator);

		const BValue& separatorSizeValue = style->value(Value_Separator_Size);
		if (separatorSizeValue.valid())
			member.separatorSize = separatorSizeValue;

		const BValue& spacingValue = style->value(Value_Space);
		if (spacingValue.valid())
			member.spacing = spacingValue;

		const BValue& iconSizeValue = style->value(Value_Icon_Size);
		if (iconSizeValue.valid())
			member.iconSize = iconSizeValue;

		BIcon* arrowIcon = style->icon(Icon_Arrow_Right);
		if (arrowIcon)
			member.arrowIcon = arrowIcon;

		const BValue& arrowSizeValue = style->value(Value_Arrow_Size);
		if (arrowSizeValue.valid())
			member.arrowSize = arrowSizeValue;

		const BValue& arrowGraphValue = style->value(Value_Arrow_Graph);
		if (arrowGraphValue.valid())
			member.arrowGraph = arrowGraphValue;

	}
	for (int i = 0; i < member.items.size(); i++)
	{
		member.items[i].namedIcon = 0;
		member.items[i].dirty = true;
	}
	member.dirty = true;
	BWidget::styleEvent(event);
}
void BMenu::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BMenu::paintEvent(const BEvent& event)
{
	BWidget::paintEvent(event);

	BPainter painter(this);

	for (int i = 0; i < member.items.size(); i++)
	{
		MenuItem& item = member.items[i];
		if (item.empty())
		{
			if (item.ia_textRect.empty())
			{
				if (member.separatorImage)
				{
					painter.drawImage(item.rect, member.separatorImage);
				}
				else if (painter.setColor(Color_Separator))
				{
					painter.setLineWidth(member.separatorSize.height());
					painter.drawLine(item.rect.leftCenter(), item.rect.rightCenter());
				}
				continue;
			}
			if (member.separatorImage)
			{
				painter.drawImage(item.rect, member.separatorImage);
			}
			else if (painter.setColor(Color_Separator))
			{
				BPoint p0 = item.ia_textRect.leftCenter() - BPoint(member.spacing, 0);
				BPoint p1 = item.ia_textRect.rightCenter() + BPoint(member.spacing, 0);
				painter.setLineWidth(member.separatorSize.width());
				painter.drawLine(item.rect.leftCenter(), p0);
				painter.drawLine(item.rect.rightCenter(), p1);
			}
			if (painter.setColor(Color_Text))
			{
				if (item.t_text.size())
					painter.drawText(item.ia_textRect, item.t_text);
				else
					painter.drawText(item.ia_textRect, item.text());
			}
			continue;
		}

		bool checked = false;
		State state = State_Normal;
		StyleRole colorRole = Color_Item;
		StyleRole imageRole = Image_Item;
		StyleRole textRole = Color_Text;
		StyleRole arrowRole = Color_Arrow;
		if (item.enabled())
		{
			if (item.action && item.action->checked())
			{
				checked = true;
				colorRole = Color_Item_Checked;
				imageRole = Image_Item_Checked;
				textRole = Color_Text_Checked;
				arrowRole = Color_Arrow_Checked;
			}
			else if (item.menu && item.menu->visible())
			{
				checked = true;
				colorRole = Color_Item_Checked;
				imageRole = Image_Item_Checked;
				textRole = Color_Text_Checked;
				arrowRole = Color_Arrow_Checked;
			}
			else if (member.hoverIndex == i)
			{
				if (member.pressed)
				{
					state = State_Pressed;
					colorRole = Color_Item_Pressed;
					imageRole = Image_Item_Pressed;
					textRole = Color_Text_Pressed;
					arrowRole = Color_Arrow_Pressed;
				}
				else
				{
					state = State_Hovered;
					colorRole = Color_Item_Hovered;
					imageRole = Image_Item_Hovered;
					textRole = Color_Text_Hovered;
					arrowRole = Color_Arrow_Hovered;
				}
			}
		}
		else
		{
			state = State_Disable;
			colorRole = Color_Item_Disable;
			imageRole = Image_Item_Disable;
			textRole = Color_Text_Disable;
			arrowRole = Color_Arrow_Disable;
		}
		if (painter.setColor(colorRole))
		{
			painter.fillRect(item.rect);
		}

		if (painter.bind(imageRole))
			painter.fillRect(item.rect);
		if (const BIcon* icon = item.icon())
		{
			if (item.action && item.action->checkable() && item.action->checked())
				painter.drawImage(item.ia_imageRect, icon->image(state, true), true);
			else
				painter.drawImage(item.ia_imageRect, icon->image(state, false), true);
		}
		else if (Graph graph = item.graph())
		{
			painter.setColor(textRole);
			painter.drawGraph(item.ia_imageRect, graph);
		}
		else if (item.action && item.action->checkable())
		{
			painter.setColor(textRole);
			painter.setLineSmooth(true);
			painter.setLineWidth(2);
			if (item.action->checked())
			{
				painter.drawGraph(item.ia_imageRect % 66, Graph_Check);
			}
		}
		if (painter.setColor(textRole))
		{
			if (item.t_text.size())
				painter.drawText(item.ia_textRect, item.t_text);
			else
				painter.drawText(item.ia_textRect, item.text());
			if (item.menu)
			{
				if (member.arrowIcon)
				{
					painter.drawImage(item.arrowRect, member.arrowIcon->image(State_Normal, item.menu->visible()));
				}
				else
				{
					painter.setColor(arrowRole);
					painter.drawGraph(item.arrowRect, member.arrowGraph);
				}
			}
		}
	}
}

void BMenu::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	member.hoverIndex = -1;
	member.pressed = false;
	BWidget::mouseEnterEvent(mouseEvent);
}
void BMenu::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex != -1)
	{
		member.hoverIndex = -1;
		member.pressed = false;
		this->fresh();
	}
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BMenu::mousePressEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = true;
	this->fresh();
}
void BMenu::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	member.pressed = false;
	this->fresh();
}
void BMenu::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	int hoverIndex = member.hoverIndex;
	BPoint pos = mouseEvent.pos();
	for (int i = 0; i < member.items.size(); i++)
	{
		BRect& actRect = member.items[i].rect;
		if (actRect.contain(pos))
		{
			hoverIndex = i;
			break;
		}
	}
	if (member.hoverIndex != hoverIndex)
	{
		member.hoverIndex = hoverIndex;
		for (int i = 0; i < member.items.size(); i++)
		{
			MenuItem& item = member.items[i];
			if (item.menu)
			{
				if (i == member.hoverIndex)
				{
					if (member.expanded)
					{
						item.menu->setPos(item.rect.rightTop());
						item.menu->setParent(this);
						item.menu->popup();
						item.menu->raise();
					}
				}
				else
				{
					item.menu->hide();
				}
			}
		}
		this->fresh();
	}
}

void BMenu::mouseClickEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverIndex >= 0)
	{
		MenuItem& item = member.items[member.hoverIndex];
		if (BAction* action = item.action)
		{
			if (action->checkable())
			{
				action->setChecked(!action->checked());
			}
			action->trigger();
		}
		if (item.menu)
		{
			member.expanded = !member.expanded;
			if (member.expanded)
			{
				item.menu->setPos(item.rect.rightTop());
				item.menu->setParent(this);
				item.menu->popup();
				item.menu->raise();
			}
			else
			{
				item.menu->hide();
			}
		}
	}
	BWidget::mouseClickEvent(mouseEvent);
}
